﻿#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Globalization;

namespace Grawlix.Geometry
{
	/// <summary>
	/// A 2D circle shape.
	/// </summary>
	public class Circle : Shape
	{
		/// <summary>
		/// The centroid of the circle.
		/// </summary>
		private readonly Vector2 _centroid;

		/// <summary>
		/// The radius of the circle.
		/// </summary>
		private readonly float _radius;

		/// <summary>
		/// Create a circle by specifying centroid and radius.
		/// </summary>
		/// <param name="centroid">The centroid of the circle.</param>
		/// <param name="radius">The radius of the circle (must be
		/// positive).</param>
		/// <exception cref="InvalidGeometryException">Radius is not
		/// positive.</exception>
		public Circle(Vector2 centroid, float radius)
		{
			// Account for NaN
			if (!(radius > 0))
			{
				throw new InvalidGeometryException(
					"A circle was specified with a non-positive radius (" +
					radius + ").");
			}

			_centroid = centroid;
			_radius = radius;
		}

		/// <summary>
		/// Finds the area of the <see cref="Circle"/>.
		/// </summary>
		public override float Area
		{
			get { return (float)Math.PI * _radius * _radius; }
		}

		/// <summary>
		/// Finds an axis-aligned bounding box for the shape.
		/// </summary>
		public override Rect BoundingBox
		{
			get
			{
				var halves = new Vector2(_radius, _radius);
				return new Rect(_centroid - halves, _centroid + halves);
			}
		}

		/// <summary>
		/// The centroid (and center) of the <see cref="Circle"/>.
		/// </summary>
		/// <remarks>The centroid of a shape is its center of mass,
		/// given uniform density.</remarks>
		public override Vector2 Centroid
		{
			get { return _centroid; }
		}

		/// <summary>
		/// Finds an oriented (non-axis-aligned) bounding box for the
		/// <see cref="Circle"/>.
		/// </summary>
		public override OrientedRect OrientedBoundingBox
		{
			get { return OrientedRect.FromAxisAligned(BoundingBox); }
		}

		/// <summary>
		/// The radius of the circle.
		/// </summary>
		public float Radius
		{
			get { return _radius; }
		}

		/// <summary>
		/// Checks to see if a point is within the
		/// current <see cref="Circle"/>.
		/// </summary>
		/// <param name="point">The point to check.</param>
		/// <returns>true if within, else false.</returns>
		public override bool Contains(Vector2 point)
		{
			return (point - _centroid).Magnitude <= _radius;
		}

		/// <summary>
		/// Returns a <see cref="T:System.String" />that represents the
		/// current <see cref="T:System.Object" />.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String" /> that represents the
		/// current <see cref="T:System.Object" />.
		/// </returns>
		public override string ToString()
		{
			return string.Format(CultureInfo.InvariantCulture,
			                     "Centroid: {0}, Radius: {1}",
			                     _centroid, _radius);
		}

		/// <summary>
		/// Finds an axis-aligned bounding box for the shape after
		/// applying a <see cref="Transform2"/>.
		/// </summary>
		/// <param name="transform">The transform to apply.</param>
		/// <returns>The axis-aligned bounding box.</returns>
		public override Rect TransformBoundingBox(
			Transform2 transform)
		{
			return BoundingBox + transform.Displacement;
		}

		/// <summary>
		/// Finds an axis-aligned bounding box for the shape after
		/// applying a <see cref="Transform2"/>.
		/// </summary>
		/// <param name="transform">The transform to apply.</param>
		/// <returns>The axis-aligned bounding box.</returns>
		public override OrientedRect TransformOrientedBoundingBox(
			Transform2 transform)
		{
			return
				OrientedRect.FromAxisAligned(BoundingBox +
				                             transform.Displacement);
		}
	}
}